package kv

import (
	"context"
	"time"

	"code.byted.org/bytertc/whiteboard_server/biz/pkg/kv"
	"code.byted.org/bytertc/whiteboard_server/biz/pkg/kv/redis"
	"code.byted.org/gopkg/logs/v2/log"
	"code.byted.org/kv/goredis"
	redis2 "code.byted.org/kv/redis-v6"
	"github.com/alicebob/miniredis"
)

func init() {
	mr, err := miniredis.Run()
	if err != nil {
		panic(err)
	}

	log.V1.Info("mini redis addr : %s", mr.Addr())
	addr = mr.Addr()

}

var addr string
var mr *miniredis.Miniredis

func NewClient() *goredis.Client {
	option := goredis.NewOption()
	option.DisableGDPRVerify = true
	var client *goredis.Client
	var err error
	for i := 0; i < 5; i++ {
		client, err = goredis.NewClientWithServers("", []string{addr}, option)
		if err != nil {
			time.Sleep(time.Millisecond * 50)
			continue
		}
		break
	}
	if err != nil {
		panic(err)
	}

	_, err = client.Ping().Result()
	if err != nil {
		panic(err)
	}
	return client
}

func NewRedis() *Storage {
	option := goredis.NewOption()
	option.DisableGDPRVerify = true
	var client *goredis.Client
	var err error
	for i := 0; i < 5; i++ {
		client, err = goredis.NewClientWithServers("", []string{addr}, option)
		if err != nil {
			time.Sleep(time.Millisecond * 50)
			continue
		}
		break
	}
	if err != nil {
		panic(err)
	}

	_, err = client.Ping().Result()
	if err != nil {
		panic(err)
	}

	return &Storage{
		Server:  mr,
		Storage: redis.New(client),
		Client:  client,
	}
}

var _ kv.Storage = (*Storage)(nil)

type Storage struct {
	Server *miniredis.Miniredis
	Client *goredis.Client
	kv.Storage
}

func (s *Storage) NewPipeline(ctx context.Context) kv.Pipeline {
	p := s.Storage.NewPipeline(ctx)
	return &pipeline{Pipeline: p}
}

var cas = `
local oldValue = redis.call('get',KEYS[1])
if not oldValue then
	return -1
else
	if oldValue == ARGV[1] then 
		redis.call('set',KEYS[1],ARGV[2])
		return 1
	else
		return 0
	end
end`

func (s *Storage) CompareAndSet(ctx context.Context, key, oldValue, newValue string, expiration time.Duration) (kv.RetType, error) {
	log.V1.CtxInfo(ctx, "CAS %s from %s to %s", key, oldValue, newValue)
	ret, err := s.Client.Eval(cas, []string{key}, oldValue, newValue).Result()
	if err != nil {
		return kv.Fail, err
	}
	v, _ := ret.(int64)
	return kv.RetType(v), nil
}
func (s *Storage) SetNX(ctx context.Context, key, value string, expiration time.Duration) (ok bool, err error) {
	log.V1.CtxInfo(ctx, "setnx %s val %s", key, value)
	return s.Client.SetNX(key, value, expiration).Result()
}

var cad = `local oldValue = redis.call('get',KEYS[1])
if not oldValue then
	return -1
else
	if oldValue == ARGV[1] then 
		redis.call('del',KEYS[1])
		return 1
	else
		return 0
	end
end
`

func (s *Storage) CompareAndDelete(ctx context.Context, key, value string) (kv.RetType, error) {
	log.V1.CtxInfo(ctx, "CAD %s val %s", key, value)
	ret, err := s.Client.Eval(cad, []string{key}, value).Result()
	if err != nil {
		return kv.Fail, err
	}
	v, _ := ret.(int64)
	log.V1.CtxInfo(ctx, "CAD ret:%+v", kv.RetType(v))
	return kv.RetType(v), nil
}

func (s *Storage) HGetSet(ctx context.Context, key, field string, value interface{}) (string, error) {
	oldValue, err := s.Client.HGet(key, field).Result()
	if err != nil {
		return "", err
	}
	_, err = s.Client.HSet(key, field, value).Result()
	return oldValue, err
}

func (s *Storage) HScan(ctx context.Context, key, match string, cursor uint64, count int64) ([]string, uint64, error) {
	keys, newCursor, err := s.Client.HScan(key, cursor, match, count).Result()
	return keys, newCursor, err
}

func (s *Storage) HGet(ctx context.Context, key, field string) (string, error) {
	value, err := s.Client.HGet(key, field).Result()
	return value, err
}

func (s *Storage) HDel(ctx context.Context, key string, field ...string) (int64, error) {
	value, err := s.Client.HDel(key, field...).Result()
	return value, err
}

func (s *Storage) HLen(ctx context.Context, key string) (int64, error) {
	return s.Client.HLen(key).Result()
}

func (s *Storage) ZRem(ctx context.Context, key string, members ...interface{}) error {
	_, err := s.Client.ZRem(key, members...).Result()
	return err
}
func (s *Storage) ZAdd(ctx context.Context, key string, members ...kv.ZMember) error {
	z := make([]redis2.Z, len(members))
	for idx, entry := range members {
		z[idx] = redis2.Z{
			Score:  entry.Score,
			Member: entry.Member,
		}
	}
	_, err := s.Client.ZAdd(key, z...).Result()
	return err
}
func (s *Storage) ZRangeWithScore(ctx context.Context, key string, start, stop int64) ([]kv.ZMember, error) {
	z, err := s.Client.ZRangeWithScores(key, start, stop).Result()
	if err != nil {
		return nil, err
	}
	members := make([]kv.ZMember, len(z))
	for idx, entry := range z {
		members[idx] = kv.ZMember{
			Member: entry.Member,
			Score:  entry.Score,
		}
	}
	return members, nil
}

func (s *Storage) ZScore(ctx context.Context, key, member string) (float64, error) {
	return s.Client.ZScore(key, member).Result()
}

func (s *Storage) Clean() {
	s.Server.FlushAll()
}
